package com.wlyuan.open.application.service;

import com.alibaba.fastjson.JSON;
import com.wlyuan.core.domain.annotation.Publisher;
import com.wlyuan.core.domain.event.DomainEvents;
import com.wlyuan.core.domain.valueobj.Mobile;
import com.wlyuan.core.domain.valueobj.TenantId;
import com.wlyuan.core.domain.valueobj.UserId;
import com.wlyuan.core.exception.AssertUtils;
import com.wlyuan.mybatis.query.PageUtils;
import com.wlyuan.open.api.IOpenClientService;
import com.wlyuan.open.application.aop.annotation.Lock;
import com.wlyuan.open.application.translator.OpenApiTranslator;
import com.wlyuan.open.core.exception.OpenClientError;
import com.wlyuan.open.core.exception.OpenClientNotFoundException;
import com.wlyuan.open.core.exception.OpenClientRegisterError;
import com.wlyuan.open.domain.api.service.OpenApiService;
import com.wlyuan.open.domain.client.OpenClient;
import com.wlyuan.open.domain.client.OpenClientId;
import com.wlyuan.open.domain.client.OpenClientPattern;
import com.wlyuan.open.domain.client.event.ApprovedEvent;
import com.wlyuan.open.domain.client.event.ClientBindEvent;
import com.wlyuan.open.domain.client.factory.OpenClientFactory;
import com.wlyuan.open.domain.client.respository.OpenClientCacheRepository;
import com.wlyuan.open.domain.client.respository.OpenClientRepository;
import com.wlyuan.open.domain.client.service.OpenClientService;
import com.wlyuan.open.domain.qrcode.service.QrCodeService;
import com.wlyuan.open.domain.source.SourceClass;
import com.wlyuan.open.domain.source.SourcePattern;
import com.wlyuan.open.domain.source.SourceRelation;
import com.wlyuan.open.domain.source.repository.SourceRepository;
import com.wlyuan.open.dto.OpenClientRegisterDTO;
import com.wlyuan.open.dto.api.*;
import com.wlyuan.open.remote.facade.Tenant;
import com.wlyuan.open.remote.facade.TenantFacade;
import com.wlyuan.open.remote.facade.UserFacade;
import com.wlyuan.open.utils.OpenClientDetailBuilder;
import com.wlyuan.open.vo.OpenClientDetailVO;
import com.wlyuan.utils.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author yuanzheng
 */
@DubboService
@Slf4j
@RequiredArgsConstructor
public class OpenClientServiceImpl implements IOpenClientService {
    private final SourceRepository sourceRepository;
    private final OpenClientRepository openClientRepository;
    private final OpenClientFactory openClientFactory;
    private final OpenApiService openApiService;
    private final TenantFacade tenantFacade;
    private final UserFacade userFacade;
    private final OpenClientCacheRepository openClientCacheRepository;
    private final QrCodeService qrCodeService;
    private final OpenClientService openClientService;
    private static final TenantId ZERO_TENANT_ID = new TenantId(0L);
    private static final UserId ZERO_USER_ID = new UserId(0L);

    private void createRelation(String sourceKey, Tenant tenant) {
        SourceRelation relation = new SourceRelation();
        relation.setSourceKey(sourceKey);
        relation.setSourceClass(SourceClass.TENANT);
        relation.setTargetId(tenant.getTenantId());
        relation.setCreateBy(ZERO_USER_ID);
        relation.setTenantId(ZERO_TENANT_ID);
        sourceRepository.save(relation);
    }

    private Tenant createTenant(OpenClientRegisterDTO register, OpenClient openClient) {
        // 如果sourceKey已绑定租户，则直接返回租户信息
        var pattern = new SourcePattern(register.getSourceKey(), SourceClass.TENANT, ZERO_TENANT_ID);
        var relation = sourceRepository.ofPattern(pattern);
        if (null != relation) {
            var tenant = tenantFacade.getTenantById(relation.getTargetId());
            openClient.bind(new TenantId(tenant.getTenantId()), new UserId(0L));
            openClientRepository.save(openClient);
            return tenant;
        }
        return tenantFacade.register(register);
    }

    private Tenant restoreTenant(OpenClient openClient) {
        if (null != openClient.getTenantId()) {
            return tenantFacade.getTenantById(openClient.getTenantId().getId());
        }
        // FIXME: 如果没有绑定租户信息，则从原始数据中创建租户并绑定
        var register = JSON.parseObject(openClient.getSourceRaw(), OpenClientRegisterDTO.class);
        return createTenant(register, openClient);
    }

    /**
     * auth调用，需要返回appSecret
     *
     * @param appId
     * @return
     */
    @Override
    public OpenClientDetailVO getOpenClientByAppId(String appId) {
        //返回空，auth授权登录提示
        var client = openClientCacheRepository.get(appId);
        if (null == client) {
            logger.info("开放租户不存在: {}", appId);
            return null;
        }
        return client;
    }

    @Override
    public OpenClientDetailVO getOpenClientByTenantId(Long tenantId, Long userId) {
        var client = openClientRepository.ofTenantId(tenantId, userId);
        if (null == client) {
            logger.info("开放租户不存在: {}", tenantId);
            throw new OpenClientError("开放租户不存在");
        }

        var tenant = tenantFacade.getTenantById(tenantId);
        AssertUtils.assertNotNull(tenant, new OpenClientError("开放租户不存在"), client);
        return new OpenClientDetailBuilder(tenant)
                .withClient(client)
                .build();
    }

    @Override
    public OpenClientDetailVO getOpenClientByMobile(String mobile) {
        OpenClientLoginDTO login = new OpenClientLoginDTO();
        login.setMobile(mobile);
        return login(login);
    }

    @Override
    @Lock(name = "注册", prefix = "REGISTER", value = "#register.mobile")
    public OpenClientDetailVO register(OpenClientLoginDTO register) {
        logger.info("注册开放平台: {}", register);
        Mobile mobile = new Mobile(register.getMobile());
        var pattern = new OpenClientPattern(mobile.getValue(), "");
        var application = openClientRepository.ofPatternMobile(pattern);
        if (application != null) {
            //待审核，提示待审核
            if (application.hasAuditing()) {
                throw new OpenClientError("账号审核中");
            }
            //账号被禁用
            if (application.hasDisable()) {
                throw new OpenClientError("账号已被禁用");
            }
            //状态不是拒绝，则提示已注册
            if (!application.hasReject()) {
                throw new OpenClientRegisterError();
            }
            //拒绝后重新注册,修改数据
            application.init(mobile);
            openClientRepository.save(application);
            return new OpenClientDetailBuilder(application).withClient(application).noWithAppSecret().build();
        }
        //创建应用
        application = openClientFactory.create(register);
        openClientRepository.save(application);
        return new OpenClientDetailBuilder(application).withClient(application).noWithAppSecret().build();
    }

    @Override
    public OpenClientDetailVO login(OpenClientLoginDTO login) {
        logger.info("登录开放平台: {}", login);
        Mobile mobile = new Mobile(login.getMobile());
        var pattern = new OpenClientPattern(mobile.getValue(), "");
        var application = openClientRepository.ofPatternMobile(pattern);
        return Optional.ofNullable(application).map(client -> {
            return Optional.ofNullable(client).filter(x -> x.bind())
                    .map(x -> new OpenClientDetailBuilder(client).withClient(client).withUser(userFacade.getById(client.getUserId().getId())).noWithAppSecret().build())
                    .orElse(new OpenClientDetailBuilder(client).withClient(client).noWithAppSecret().build());
        }).orElse(null);
    }

    @Override
    @Lock(name = "切换手机号", prefix = "UNBINDMOBILE", value = "#unbind.mobile")
    public OpenClientDetailVO unbindMobile(OpenClientUnbindMobileDTO unbind) {
        var application = openClientRepository.ofAppId(unbind.getAppId());
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), unbind);
        openClientService.unbindMobile(application, unbind);
        openClientCacheRepository.remove(application.getAppId());
        return new OpenClientDetailBuilder(application).withClient(application).noWithAppSecret().build();
    }

    /**
     * 客户端调用，不返回appSecret
     *
     * @param appId
     * @return
     */
    @Override
    public OpenClientDetailVO getOpenClient(String appId) {
        var application = openClientRepository.ofAppId(appId);
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), appId);
        var apiList = openApiService.getOpenApiByClientId(application.getId());
        return Optional.ofNullable(application.getUserId()).map(u -> {
            var user = userFacade.getById(u.getId());
            AssertUtils.assertNotNull(user, new OpenClientError("开放账号不存在"), application);
            return new OpenClientDetailBuilder(application).withClient(application).noWithAppSecret().withApiList(OpenApiTranslator.translateClientApi(apiList)).desensitizedMobile().withUser(user).build();
        }).orElse(new OpenClientDetailBuilder(application).withClient(application).noWithAppSecret().withApiList(OpenApiTranslator.translateClientApi(apiList)).desensitizedMobile().build());
    }

    @Override
    @Publisher
    public OpenClientDetailVO changeAppSecret(OpenClientChangeAppSecretDTO change) {
        var application = openClientRepository.ofAppId(change.getAppId());
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), change);
        openClientService.changeAppSecret(application);
        openClientCacheRepository.remove(application.getAppId());
        return new OpenClientDetailBuilder(application).noWithAppSecret().build();
    }

    @Override
    @Publisher
    @Lock(name = "绑定", prefix = "BIND", value = "#bind.qrCodeId")
    @Transactional(rollbackFor = Exception.class)
    public OpenClientDetailVO bind(OpenClientBindDTO bind) {
        var qrCode = qrCodeService.check(bind.getQrCodeId());
        if (StringUtils.isBlank(qrCode.getAppId())) {
            logger.warn("二维码已失效：{}", bind);
            throw new OpenClientError("二维码已失效");
        }
        bind.setAppId(qrCode.getAppId());
        var application = openClientRepository.ofAppId(bind.getAppId());
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), bind);
        var user = userFacade.check(bind.getAccount());
        //已经绑定，并且是同一个账号，则不需要重复绑定
        if (application.bind() && application.getUserId().equals(user.getUserId())) {
            logger.warn("已绑定了账号：{},{}", bind, application);
            throw new OpenClientError("已绑定了管理员账号,请勿重复绑定");
        }
        var afterClient = openClientRepository.ofTenantId(user.getTenantId().getId(), user.getUserId().getId());
        openClientService.bind(application, user, afterClient);
        openClientCacheRepository.remove(application.getAppId());
        DomainEvents.add(new ClientBindEvent(bind.getQrCodeId(), application, afterClient));
        return new OpenClientDetailBuilder(application).noWithAppSecret().build();

    }

    @Override
    public String getAppSecret(String appId) {
        var application = openClientRepository.ofAppId(appId);
        return openClientFactory.getAppSecretKey(application);
    }

    @Override
    public Page<OpenClientDetailVO> search(OpenClientFilterDTO filter) {
        var result = openClientRepository.search(filter);
        return PageUtils.as(result, openClientRepository.list(result.getRecords()).stream().map(a -> {
            return new OpenClientDetailBuilder(a).withClient(a).noWithAppSecret().build();
        }).collect(Collectors.toList()));
    }

    @Override
    @Publisher
    public void approved(OpenClientApprovedDTO approved) {
        var openClientId = new OpenClientId(approved.getClientId());
        var application = openClientRepository.ofId(openClientId);
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), openClientId);
        application.approved(approved.getUser());
        openClientRepository.save(application);
        openClientCacheRepository.remove(application.getAppId());
        DomainEvents.add(new ApprovedEvent(approved, application));
    }

    @Override
    public void reject(OpenClientRejectDTO reject) {
        var openClientId = new OpenClientId(reject.getClientId());
        var application = openClientRepository.ofId(openClientId);
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), openClientId);
        application.rejected(reject.getUser(), reject.getReason());
        openClientRepository.save(application);
        openClientCacheRepository.remove(application.getAppId());
    }

    @Override
    public void enable(OpenClientEnableDTO enable) {
        var openClientId = new OpenClientId(enable.getClientId());
        var application = openClientRepository.ofId(openClientId);
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), openClientId);
        application.enable(enable.getUser());
        openClientRepository.save(application);
        openClientCacheRepository.remove(application.getAppId());
    }

    @Override
    public void disable(OpenClientDisableDTO disable) {
        var openClientId = new OpenClientId(disable.getClientId());
        var application = openClientRepository.ofId(openClientId);
        AssertUtils.assertNotNull(application, new OpenClientNotFoundException(), openClientId);
        application.disable(disable.getUser());
        openClientRepository.save(application);
        openClientCacheRepository.remove(application.getAppId());
    }

    @Override
    public String getUserMobile(String account) {
        return userFacade.getMobile(account);
    }
}
